#include "waypointlist.h"
using namespace FsxSimConnect;

//
// Constructor
//
WaypointList::WaypointList() : QList<Waypoint>()
{
    // code goes here
}

//
// Init by other waypoints (deep copy)
//
WaypointList::WaypointList(const WaypointList &otherWaypoints) {
    this->initByOtherWaypoints(otherWaypoints);
}

//
// Assignment operator
//
WaypointList &WaypointList::operator =(const WaypointList &otherWaypointList)
{
    // Check for self-assignment!
    if (this == &otherWaypointList)  return *this; // Same object? Yes, so skip assignment, and just return *this
    this->initByOtherWaypoints(otherWaypointList);
    return *this;
}

//
// Init by other waypoints
//
void WaypointList::initByOtherWaypoints(const WaypointList &otherWaypoints) {

    // lock both lists
    QMutexLocker otherLocker(&otherWaypoints._mutex);
    QMutexLocker locker(&_mutex);

    // copy over, deep copy
    WaypointList::clear();
    int c = otherWaypoints.count();
    for (int i=0; i<c; i++) {
        WaypointList::append(otherWaypoints.at(i));
    }
}

// http://www.parashift.com/c++-faq-lite/separate-template-fn-defn-from-decl.html
template Waypoint WaypointList::updateOrAppend(const FSXAircraftInstrumentationData &data, qint64 timeFrameMs);
template Waypoint WaypointList::updateOrAppend(const FSXAircraftPositionAndSpeedData &data, qint64 timeFrameMs);

//
// We assume the list is ordered and the last values are at the end
//
template <class DATASTRUCT> Waypoint WaypointList::updateOrAppend(const DATASTRUCT &data, qint64 timeFrameMs)
{
    QMutexLocker locker(&_mutex);
    int c = WaypointList::count();
    if (c < 1)
        WaypointList::append(Waypoint(data));
    else {
        c--; // count to index
        Waypoint wpLast = WaypointList::last();
        if (timeFrameMs >=0 && wpLast.isWithinTimeFrame(timeFrameMs)) {
            wpLast.update(data);
            // Remove last if nothing has changed,
            // removes identical waypoints at the end
            if (c > 0 && !(wpLast.hasChanged((*this)[c-1])))
                WaypointList::removeLast();
            else
                (*this)[c] = wpLast;
        }
        else {
            WaypointList::append(Waypoint(data));  // incomplete WP at this time
        }
    }
    return WaypointList::last();
}

//
// Update the deltas
//
void WaypointList::updateDeltas(const WaypointList& waypoints, bool fullBuild) {

    int deltaCount = WaypointList::count(); // target
    int wpCount = waypoints.count(); // source
    int startindex = 1;

    // check if not a full build
    if (!fullBuild) {
        // Consistent? Otherwise do full build from the start
        if (deltaCount < wpCount) {
            startindex = wpCount - 1; // last element in waypoints
        } else {
            fullBuild = true; // forced full build, because inconsistent
        }
    }

    if (fullBuild) {
        if (deltaCount > 0) WaypointList::clear();
        deltaCount = 0;
    }

    if (waypoints.length() < 2) return;

    for (int i =startindex; i < wpCount; i++) {
        Waypoint wo = waypoints.at(i-1);
        Waypoint wn = waypoints.at(i);
        Waypoint d = Waypoint(wn, wo);
        if (deltaCount == wpCount -1)
            WaypointList::replace(i-1,d); // update existing index
        else
            WaypointList::append(d);
    }
}

//
// Calculate a average of waypoints
//
Waypoint WaypointList::average(const int numberLastWaypoints, int &actualCount) const {

    int c = WaypointList::count();
    if (c<1) {
        actualCount = 0;
        return Waypoint();
    }
    int start = c-numberLastWaypoints;
    if (start < 0) start =0;
    actualCount = 1;
    Waypoint wp = WaypointList::at(start++);

    // add values
    for (int i = start; i < c; i++) {
        actualCount++;
        wp += WaypointList::at(i);
    }
    wp /= actualCount;
    return wp;
}

//
// Remove the oldest values from the list (at the beginning)
//
int WaypointList::reduce(const int maxNumber) {
    if (maxNumber >= WaypointList::count()) return 0; // nothing to do

    // see http://www.qtcentre.org/threads/7361-Remove-first-n-elements-from-QList
    QMutexLocker locker(&_mutex);
    int c = WaypointList::count();
    int diff = c-maxNumber; // elements (to be) removed
    for (int i=0; i < diff; i++) {
        WaypointList::removeFirst();
    }
    return diff;
}

//
// List will only contain elements with timestamp >= given timestamp
//
int WaypointList::reduceToNewerOrEqual(const qint64 timestampUtcEpoch) {
    int c = WaypointList::count();
    if (c==0) return 0; // nothing to reduce
    int firstIndex = WaypointList::waypointsSince(timestampUtcEpoch);
    if (firstIndex < 0) {
        // no waypoint is newer
        WaypointList::clear();
        return c; // all have been cleared
    }
    return WaypointList::reduce(c-firstIndex);
}

//
// Remove waypoints not in the timeframe
//
int WaypointList::reduceToNewerOrEqualRelative(const qint64 timeframeMs)
{
    if (timeframeMs <0) return 0;
    int c = WaypointList::count();
    if (c<2) return 0; // nothing to reduce
    Waypoint wp = WaypointList::last();
    qint64 timestampUtcEpoch = wp.getTimestampUtcEpoch() - timeframeMs;
    wp = WaypointList::first(); // oldest waypoint
    if (wp.isNewerOrEqual(timestampUtcEpoch)) return 0;
    return WaypointList::reduceToNewerOrEqual(timestampUtcEpoch);
}

//
// Reduce the resolution
//
WaypointList WaypointList::reducedResolution(const qint64 resolutionMs) const
{

    int c = WaypointList::count();

    // entry check, resolution too small, not enough waypoints?
    if (resolutionMs < WPMINREDUCEDRESOLUTION || c <2) return WaypointList(*this);

    // Build new list
    WaypointList wps;
    Waypoint wp = WaypointList::first();
    qint64 minTime = wp.getTimestampUtcEpoch() + resolutionMs;
    wps.append(wp);
    for (int i=1;i<c;i++) {
        wp = WaypointList::at(i);
        if (wp.isNewerOrEqual(minTime)) {
            minTime += resolutionMs;
            wps.append(wp);
        }
    }
    return wps;
}

//
// Index of the waypoint >= timestamp
//
int WaypointList::waypointsSince(const qint64 timestampUtcEpoch) {
    int c = WaypointList::count();
    if (c<1) return -1;
    if (!WaypointList::last().isNewerOrEqual(timestampUtcEpoch)) return -1; // even the last one is not newer
    int r = -1;
    for (int i=0; i< c; i++) {
        Waypoint wp = WaypointList::at(i);
        if (wp.isNewerOrEqual(timestampUtcEpoch)) {
            r = i;
            break;
        }
    }
    return r;
}
